import datetime
import json
import functools
import random
import traceback
from copy import deepcopy

from werkzeug.security import check_password_hash, generate_password_hash

from flask_login import login_user, logout_user, current_user

from app.chess import login_manager
from app.read_conf import pika_server
# from app.chess import pika_pool
from app import tools
from app import dbop
from app import error
from app import http_req
# from app import tasks
from app.rule import ChessGame
from app.val_def import *


@login_manager.user_loader
def load_user(user_id):
    rv = dbop.UserDAO.get_record(user_id)
    return rv


class RequireRole(object):

    def __init__(self, role=None):
        self.role = role

    def __call__(self, method):
        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            if not current_user.is_authenticated:
                return {
                    'error': error.E_NOT_LOGIN
                }
            elif self.role:  # and current_user.type not in self.role:
                pass
                if isinstance(self.role, list):  # and current_user.type not in self.role:
                    if current_user.role not in self.role:
                        return {
                            'error': error.E_USER_TYPE_WRONG
                        }
                elif current_user.role != self.role:
                    print('user.type:', self.role)    #, current_user.type
                    return {
                        # 'error': err_map.get(self.role)
                        'error': error.E_USER_TYPE_WRONG
                    }

            try:
                rv = method(*args, **kwargs)
            except Exception as e:
                # print e.message
                print(traceback.format_exc())
                dbop.session_rollback()
                return {
                    'error': error.E_PROC_WRONG
                }

            return rv

        return wrapper


require_login = RequireRole()


def api_create_game():
    # param = tools.proc_request_data()
    token = "testmatch123sdfeadde_red"
    dbop.ChessMatchDAO.update_record(None, {
        "red_player": 1,
        "black_player": -1,
        "token": token,
        "state": "OnGoing",
        "create_time": datetime.datetime.now()
    })

    return {
        "error": 0
    }


def api_start_ai_chess():
    params = tools.proc_request_data()
    red_sel = params.get("red_sel")
    level = params.get("level")
    print("api_start_ai_chess:", red_sel, level, current_user.id)
    if red_sel:
        red_player = current_user.id
        black_player = -red_player
    else:
        black_player = current_user.id
        red_player = -black_player
    token = tools.get_random_str(16)
    dbop.ChessMatchDAO.update_record(None, {
        "token": token,
        "red_player": red_player,
        "black_player": black_player,
        "state": "OnGoing",
        "param": tools.safe_json_dump({
            "level": level
        }),
        "create_time": datetime.datetime.now()
    })
    match = dbop.ChessMatchDAO.get_record_by_filter_key({
        "token": token
    })
    return {
        "error": 0,
        "match_id": match.id
    }


def api_chess_game_process():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        params["seq"] = 1
        game = ChessGame()
    else:
        params["seq"] = records[-1].seq + 1
        last_record = records[-1]
        status_json = tools.safe_json_loads(last_record.status)
        game = ChessGame(status_json)

    # game = load_chess_game_records(records)
    # if 'Error.ProcWrong' == game:
    #     return {
    #         'error': error.E_PROC_WRONG
    #     }

    move = params.get('move')
    move = (move[0], tuple(move[1]), tuple(move[2]))
    # 只初步检查了基本的走棋规则， 但是没有检查将军的情况， 也没有实际改变状态。
    res = game.step_check(move)
    if 'OK' != res:
        return {
            "error": error.E_PARAM_WRONG,
            'err_msg': res
        }
    # 检查对将的情况和将军的情况。
    target_piece = game.change_status_raw(move)
    # 下面这个函数是否放在 change_status_raw 里面比较好。
    game.build_piece_index()
    w_same_road = game.check_w_same_road()
    print("check_w_same_road:", w_same_road)
    # 对将， 需要复原。
    if w_same_road:
        game.rewind_status_raw(move, target_piece)
        return {
            "error": error.E_FORBID_MOVE,
            "err_msg": "Error.SameRoadW"
        }

    # 检查是否有将军的情况
    result = game.get_piece_attack()
    print('attack info:', result)
    attack_info_backward = result.get('attack_info_backward').get(game.cur_player)

    # w_attacked = game.check_w_attacked(game.cur_player)
    w_attacked = (game.cur_player + 'W') in attack_info_backward
    print("w_attacked:", w_attacked)
    if w_attacked:
        game.rewind_status_raw(move, target_piece)
        return {
            "error": error.E_FORBID_MOVE,
            "err_msg": "Error.AttackedW"
        }
    # 可以最终确定走子。
    game.switch_player()

    data = game.get_bord_status()

    # 已经切换了执棋方
    attack_info_backward = result.get('attack_info_backward').get(game.cur_player)
    w_attacked = (game.cur_player + 'W') in attack_info_backward
    # 只有将军了才去检查是否杀死了比赛
    if w_attacked:
        # 检查有没有杀死比赛
        kill_game = game.check_kill_game()
        # game.print_status()

        data['attack_w'] = True
        data['kill_game'] = kill_game
    else:
        data['attack_w'] = False
        data['kill_game'] = False
    data['attack_info_backward'] = result.get('attack_info_backward')

    print('data:', data)

    # 保存记录
    params['move'] = json.dumps(params.get('move'))
    params['dead_piece'] = target_piece
    params['status'] = json.dumps(data)
    params['update_time'] = datetime.datetime.now()
    dbop.ChessGameProcessDAO.update_record(None, params)

    # if 'OK' != res:
    #     return {
    #         'error': error.E_PARAM_WRONG,
    #         'err_msg': res
    #     }
    # #
    # params['move'] = json.dumps(params.get('move'))
    # params['update_time'] = datetime.datetime.now()
    # dbop.ChessGameProcessDAO.update_record(None, params)
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    # data = game.get_bord_status()
    # # 判断是否将军， 如果将军， 对方是否有解法， 如果没有解法，提示绝杀。
    # cur_player = data.get('player')
    # attack_reverse = data.get('player_attack_reverse')[cur_player]
    # piece_w = cur_player + 'W'
    # print('attack_reverse:', piece_w, attack_reverse, data)
    # if piece_w in attack_reverse:
    #     # 对方被将军， 判断是否是死将
    #     copy_game = deepcopy(game)
    #     if copy_game.check_dead_game():
    #         # print("dead game")
    #         data['dead_game'] = True
    #         game.dead_game = True
    # 机器人走棋的代码， 现在不用。
    # match_record = dbop.ChessMatchDAO.get_record(match_id)
    # if match_record.red_player < 0 or match_record.black_player < 0:
    #     from app import tasks
    #     # 通知机器人走棋
    #     tasks.match_player.delay()

    return {
        "error": 0,
        "data": data
    }


def api_wait_opponent_move():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    seq = params.get('seq')
    player = params.get('player')
    if not match_id or seq is None or not player:
        return {
            "error": error.E_PARAM_WRONG
        }

    record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
        'match_id': match_id,
        'seq': seq
    })
    if seq and not record:
        return {
            "error": error.E_PROC_WRONG
        }

    end_dt = datetime.datetime.now() + datetime.timedelta(seconds=120)
    while True:
        dt = datetime.datetime.now()
        if dt > end_dt:
            return {
                "error": error.E_PROC_TIMEOUT
            }

        move_record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
            'match_id': match_id,
            'seq': seq + 1
        })
        # print('move_record:', move_record, dt, seq + 1)
        if move_record:
            # all_records = dbop.ChessGameProcessDAO.get_match_record(match_id)
            # game = load_chess_game_records(all_records)
            # if 'Error.ProcWrong' == game:
            #     return {
            #         "error": error.E_PROC_WRONG
            #     }
            #
            # game.build_piece_attack('R')
            # game.build_piece_attack('B')
            data = tools.safe_json_loads(move_record.status)

            return {
                "error": 0,
                "move": tools.safe_json_loads(move_record.move),
                "data": data
            }
        tools.app_sleep(0.5)


def api_wait_pika_move():
    params = tools.proc_request_data()
    match_id = params.get("match_id")
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        params["seq"] = 1
        game = ChessGame()
    else:
        params["seq"] = records[-1].seq + 1
        last_record = records[-1]
        status_json = tools.safe_json_loads(last_record.status)
        game = ChessGame(status_json)

    # 判断一下是否应该由机器人走。
    match_record = dbop.ChessMatchDAO.get_record(match_id)
    if not match_record:
        return {
            "error": error.E_PARAM_WRONG
        }
    #
    if (game.cur_player == "R" and match_record.red_player > 0) or \
            (game.cur_player == "B" and match_record.black_player > 0):
        return {
            "error": error.E_PARAM_WRONG
        }
    param = tools.safe_json_loads(match_record.param)
    level = int(param.get("level", 1))
    # 判断如果 pika走了三个回合相同的棋。临时调高 level , 避免长将。
    if len(records) > 12 and records[-2].move == records[-6].move == records[-10].move and \
            records[-4].move == records[-8].move == records[-12].move:
        print("pika long check!")
        level = 5

    #
    uci_pos = game.get_uci_pos()
    print("uci_pos:", uci_pos, game.cur_player)
    game.print_status()
    # best move b9c7 翻译成 本项目的表示法，翻译成黑方， 马二进三
    uci_cmd = "position fen %s %s - - 0 1" % (uci_pos, {"B": "b", "R": "w"}.get(game.cur_player))
    print("uci_cmd:", uci_cmd)

    try:
        # 怎么用微服务调用的形式实现。
        res = http_req.http_post_v2(pika_server + "/api/v2.0/get_pika_move", {
            "uci_cmd": uci_cmd,
            "depth": level * 2
        })
        print("res:", res)
        uci_coord = "1111"
        if 0 == res.get("error"):
            uci_coord = res.get("move")
            move = game.get_move_from_uci_coord(game.cur_player, uci_coord)
            #
            check_res = game.step_check(move)
            print("check_res:", check_res)
            target_piece = game.change_status_raw(move)
            # 检查对将和被将的情况。 然后 切换状态。记录到数据库里。
            result = game.get_piece_attack()
            print('attack info:', result)

            game.switch_player()
            data = game.get_bord_status()

            # 已经切换了执棋方
            attack_info_backward = result.get('attack_info_backward').get(game.cur_player)
            w_attacked = (game.cur_player + 'W') in attack_info_backward
            # 只有将军了才去检查是否杀死了比赛
            if w_attacked:
                # 检查有没有杀死比赛
                kill_game = game.check_kill_game()
                # game.print_status()

                data['attack_w'] = True
                data['kill_game'] = kill_game
            else:
                data['attack_w'] = False
                data['kill_game'] = False
            data['attack_info_backward'] = result.get('attack_info_backward')

            print('data:', data)

            # 保存记录
            params['move'] = json.dumps(move)
            params['dead_piece'] = target_piece
            params['status'] = json.dumps(data)
            params['update_time'] = datetime.datetime.now()
            dbop.ChessGameProcessDAO.update_record(None, params)

            return {
                "error": 0,
                "move": move,
                "data": data
            }
    except Exception as e:
        print(e)

    return {
        "error": 0
    }


def api_get_pika_move():
    pass

    return {
        "error": 0
    }


def api_load_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    records = dbop.ChessGameProcessDAO.get_match_last_record(match_id)
    print("match last records:", records)
    if not records:
        game = ChessGame()
        data = game.get_bord_status()
    else:
        status_json = tools.safe_json_loads(records.status)
        data = status_json

    # from app import tasks
    # tasks.match_player()

    # if not records:
    #     return {
    #         'error': error.E_NO_RECORDS
    #     }
    #
    # print("start load game ...")
    # game = load_chess_game_records(records)
    # if 'Error.ProcWrong' == game:
    #     return {
    #         'error': error.E_PROC_WRONG
    #     }
    # print("finish load game ...")

    # print("start build...")
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    # print("finish build...")
    # data = game.get_bord_status()

    return {
        'error': 0,
        'data': data
    }


def get_match_history():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    print('records:', len(records))
    data = [{
        'seq': x.seq,
        'move': x.move,
        'dead_piece': x.dead_piece,
        'status': tools.safe_json_loads(x.status),
        # 'move': x.move,
    } for x in records]

    return {
        "error": 0,
        "data": data
    }


def cal_machine_move():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    seq = params.get('seq')
    #
    record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
        'match_id': match_id,
        'seq': seq
    })
    print('record:', record)
    data_json = tools.safe_json_loads(record.status)
    kill_game = data_json.get('kill_game')
    if kill_game:
        return {
            "error": error.E_PARAM_WRONG
        }
    game = ChessGame(data_json)
    from app.machine import machine_move_select
    res = machine_move_select(game)
    print("res:", res)

    return {
        "error": 0,
        "data": res
    }


def api_reset_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    dbop.ChessGameProcessDAO.delete_match_record(match_id)

    game = ChessGame()
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    data = game.get_bord_status()
    return {
        'error': 0,
        'data': data
    }


def api_rewind_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    # step = params.get('step')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        return {
            'error': error.E_NO_RECORDS,
            'err_msg': 'error.E_NO_RECORDS'
        }
    dbop.ChessGameProcessDAO.delete_match_record_some(match_id, records[-2:])
    if len(records) > 2:
        last_record = records[-3]
    else:
        last_record = None
    # 下面这个函数删掉了， 这个接口也要重写。
    # game = load_chess_game_records(records[:-1])
    # if 'Error.ProcWrong' == game:
    #     return {
    #         'error': error.E_PROC_WRONG
    #     }

    if not last_record:
        game = ChessGame()
        data = game.get_bord_status()
    else:
        status_json = tools.safe_json_loads(last_record.status)
        data = status_json

    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    # data = game.get_bord_status()
    return {
        'error': 0,
        'data': data
    }


def api_user_register():
    params = tools.proc_request_data()
    username = params.get('username')
    password = params.get('password')
    if not username or not password:
        return {
            'error': error.E_PARAM_WRONG
        }
    user_obj = dbop.UserDAO.get_record_by_username(username)
    if user_obj:
        return {
            'error': error.E_USER_EXIST
        }
    dbop.UserDAO.update_record(None, {
        'username': username,
        'password': generate_password_hash(password),
        'create_time': datetime.datetime.now()
    })

    return {
        "error": 0
    }


def api_user_login():
    params = tools.proc_request_data()
    username = params.get('username')
    password = params.get('password')
    if not username or not password:
        return {
            'error': error.E_PARAM_WRONG
        }
    user_obj = dbop.UserDAO.get_record_by_username(username)
    if not user_obj:
        return {
            'error': error.E_USER_NOT_EXIST
        }
    if not check_password_hash(user_obj.password, password):
        return {
            'error': error.E_PASSWORD_WRONG
        }

    login_user(user_obj)

    return {
        "error": 0
    }


def api_user_logout():
    params = tools.proc_request_data()
    return {
        "error": 0
    }


def api_test():
    params = tools.proc_request_data()

    return {
        "error": 0
    }


def api_game_enter_district():
    from app import tasks

    params = tools.proc_request_data()

    username = current_user.username
    record = dbop.GameDistrictDAO.get_record_by_username(username)
    if not record:
        # tasks
        # return {
        #     'error': error.E_STATE_WRONG
        # }
        # 计算一个随机的玩家， 红方还是黑方，
        # 0表示红方， 1表示黑方。 红方去寻找黑方的玩家。
        player = random.randint(0, 1)
        print('player:', player)

        #
        dbop.GameDistrictDAO.update_record(None, {
            'uid': current_user.id,
            'username': current_user.username,
            'state': 'Pending',
            'player': player,
            'param': tools.safe_json_dump(params),
            'update_time': datetime.datetime.now()
        })
    else:
        player = record.player

    # Todo:  在异步任务处理里面进行玩家匹配
    # 只能红方玩家去找黑方玩家，
    # 1. 会不会两个红方玩家都去找一个黑方玩家。 有可能，所以是不是还是需要在异步队列里面处理。
    # 为了避免递归导入， 只能放在这里导入。
    tasks.match_player.delay()
    for _ in range(3):
        player_record = dbop.GameDistrictDAO.get_record_by_username(username)
        print("player_record:", player_record.state)
        if player_record and 'OnChess' == player_record.state:
            # if PLAYER_RED == player:
            #     filter_key = {
            #         'red_player': player_record.uid
            #     }
            # else:
            #     filter_key = {
            #         'black_player': player_record.uid
            #     }
            # filter_key = {
            #     'state': 'OnGoing',
            #     'match_id': player_record.match_id
            # }
            match_record = dbop.ChessMatchDAO.get_record(player_record.match_id)
            print('match_record:', player_record.match_id, match_record)
            if not match_record:
                return {
                    "error": error.E_PROC_WRONG
                }
            # chess_records = dbop.ChessGameProcessDAO.get_match_record(match_record.id)
            # game = load_chess_game_records(chess_records)
            # data = game.get_bord_status()

            return {
                "error": 0,
                "match_id": match_record.id,
                'player': player,
                # "data": data
            }
        else:
            tools.app_sleep(1)

    return {
        "error": error.E_PROC_TIMEOUT
    }


def api_game_exit_district():
    return {
        "error": 0
    }
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    player = params.get('player')
    if not match_id or not player:
        return {
            "error": error.E_PARAM_WRONG
        }
    match_record = dbop.ChessMatchDAO.get_record(match_id)
    if not match_record:
        return {
            "error": error.E_PARAM_WRONG
        }

    if ('B' == player and match_record.black_player != current_user.id) or \
       ('R' == player and match_record.red_player != current_user.id):
        return {
            "error": error.E_PARAM_WRONG
        }
    # 更新比赛记录的状态
    try:
        dbop.begin_transction()
        dbop.ChessMatchDAO.update_record(match_record.id, {
            "state": "Finish"
        })
        # 删除游戏区的记录
        # 大于0 表示是人类， 小于0表示是机器
        # red_player_record = dbop.GameDistrictDAO.get_record_by_filter_key({
        #     "uid": match_record.red_player
        # })
        # dbop.delete_record(red_player_record)
        # red_player_record = dbop.GameDistrictDAO.get_record_by_filter_key({
        #     "uid": match_record.red_player
        # })
        delete_records = [dbop.GameDistrictDAO.get_record_by_filter_key({"uid": uid})
                          for uid in [match_record.red_player, match_record.black_player]]
        delete_result = [dbop.delete_record(record) for record in delete_records]
        dbop.session_commit()

    except Exception as e:
        print(traceback.format_exc())
        dbop.session_rollback()
        return {
            "error": error.E_PROC_WRONG
        }

    return {
        "error": 0
    }

